/*
 * GPSReader.java
 *
 * Created on 1 de Abril de 2007, 12:50
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package net.onmotion.gps;

import java.io.DataInputStream;
import java.io.DataOutputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import net.onmotion.Location;
import net.onmotion.util.StringTokenizer;

/**
 * 
 * @author sestevao
 */
public class GpsBt extends Gps implements Runnable {

	public int mode = 0;
	public boolean isActive = false;
	public boolean isConnected = false;
	public boolean isBTSearchComplete;
	public boolean isBTSerialPort;
	// current bluetooth device
	public String btUrl = "";
	public String btName = "";
	public String error = "No Error";

	// current connection
	StreamConnection conn = null;
	DataInputStream in = null;
	DataOutputStream out = null;

	// GPS specific
	StringBuffer sb;
	StringTokenizer tok;
	String currentInfo;
	Location location;
	
	//state
	public final static byte STATE_SEARCH_SENTENCE_BEGIN = 0;
	public final static byte STATE_READ_DATA_TYPE = 1;
	public final static byte STATE_READ_SENTENCE = 2;
	

	static GpsBt gpsReader = null;

	public static GpsBt instance() {
		if (gpsReader == null) {
			gpsReader = new GpsBt();
		}

		return gpsReader;
	}

	/** Creates a new instance of GPSReader */
	public GpsBt() {
		tok = new StringTokenizer("", "*,");
		currentInfo = "empty";
		location = new Location();
		isConnected = false;
		isActive = false;
		sb = new StringBuffer(100);
	}

	public void start() {
		if (isActive) {
			stop();
		}

		connect();

		if (isConnected) {
			isActive = true;
			Thread t = new Thread(this);
			t.start();
		}
	}

	public void stop() {
		if (isActive) {
			isActive = false;
			try {
				while (isConnected) {
					close();
					Thread.sleep(100);
				}
			} catch (Throwable t) {
				error = "stop:" + t.toString();
				close();
			}
		}
	}

	public void run() {
		isActive = true;
		while (isActive) {
			try {
			  // check if connection is still open
				if (!isConnected && isActive) {
				  // connect to gps device
					connect();
				} else {
				  // read NMEA Strings
					fetchGPSCoords();
				}
			} catch (Throwable t) {
				error = "run:" + t.toString();
				close();
			}
		}
		close();
		isActive = false;
	}

	public void fetchGPSCoords() {
		byte c = 0;
		try {
			if (!isConnected) {
				return;
			}
			int size = in.available();
			if (size <= 0) {
				return;
			}
			for (int j = 0; j < size; j++) {
				int i = in.read();
				if (i != -1) {
					char l = (char) i;
					switch (mode) {
					case (STATE_SEARCH_SENTENCE_BEGIN): {
						if (l == '$') {
							mode = 1;
							sb.setLength(0);
						}
					}
						break;
					case (STATE_READ_DATA_TYPE): {
						sb.append(l);
						if (sb.length() == 6) {
							if (sb.toString().startsWith("GPGGA")) {
								mode = STATE_READ_SENTENCE;
								sb.setLength(0);
							} else {
								mode = STATE_SEARCH_SENTENCE_BEGIN;
								sb.setLength(0);
							}
						}
					}
						break;
					case (STATE_READ_SENTENCE): {
						sb.append(l);
						if ((l == 13) || (l == 10) || (l == '$')) {
							mode = STATE_SEARCH_SENTENCE_BEGIN;
							synchronized (this) {
								currentInfo = new String(sb.toString());
							}
							Thread.sleep(1000);
						}
					}
						break;
					}

				} else {
					close();
				}
			}

		} catch (Throwable t) {
			error = "fetch" + t.toString();
			close();
		}
	}

	public Location getLocation() {
		location = new Location();
		try {
			if (isConnected && isActive && currentInfo != null) {
				synchronized (this) {					
					location = Location.parseGPGGA(currentInfo);
				}				
			}
		} catch (Throwable t) {
			error = "get:" + t.toString();
			close();
		}
		return location;
	}

	/**
	 * Connect to the bluetooth device
	 */
	public void connect() {
		if (btUrl == null || (btUrl.trim().compareTo("") == 0)) {
			isConnected = false;
			return;
		}
		try {
			conn = (StreamConnection) Connector.open(btUrl,
					Connector.READ_WRITE);			
			in = new DataInputStream(conn.openInputStream());
			isConnected = true;
			mode = 0;
			// wait form some tipe to allow connection to go trough
			Thread.sleep(1000);
		} catch (Throwable t) {
			error = "connect:" + t.toString();
			close();
		}
	}

	public void close() {
		try {
			if (in != null) {
				in.close();
			}
			if (out != null) {
				out.close();
			}
			if (conn != null) {
				conn.close();
			}
			in = null;
			out = null;
			conn = null;
		} catch (Throwable t) {
			error = "close" + t.toString();
		} finally {
			in = null;
			out = null;
			conn = null;
		}
		isConnected = false;
	}

	/**
	 * 
	 * @param btUrl - bluetooth url address
	 * @param btName - bluetooth friendly name
	 */
	public void setDevice(String btUrl, String btName) {
		try {
			this.btUrl = btUrl;
			this.btName = btName;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public boolean isConnected() {
		return isConnected;
	}

}
